package game;

import java.util.Stack;

/**
 * Title: 五子棋游戏
 * Author: ZtAxZafkiel @ gitee.com
 */
public class Z5CGame {

    public enum Camp       {none, black, white};
    public enum GameResult {accept, reject, blackWin, whiteWin, draw};

    public static class Result {
        private GameResult result;
        private int[][]    winBy;

        public Result(GameResult result, int[][] winBy) {
            this.result = result;
            this.winBy  = winBy;
        }

        public Result(GameResult result) {
            this(result, null);
        }

        public final GameResult getResult() {
            return result;
        }

        public final int[][] getWinBy() {
            return winBy;
        }
    }

    public static final Result ACCEPT = new Result(GameResult.accept, null);
    public static final Result REJECT = new Result(GameResult.reject, null);
    public static final Result DRAW   = new Result(GameResult.draw, null);

    static final byte NONE  = 0;
    static final byte BLACK = 1;
    static final byte WHITE = 2;

    private int          checkboardSize;    //棋盘大小
    private Camp         current;           //当前玩家(该谁走)
    private byte[][]     checkboard;        //棋盘
    private Stack<int[]> history;           //row, col, camp
    private Stack<int[]> checkStack;

    public final int getSize() {
        return checkboardSize;
    }

    public Z5CGame(int checkboardSize) {
        this.checkboardSize = checkboardSize;
        this.checkboard     = new byte[checkboardSize][checkboardSize];
        this.history        = new Stack<>();
        this.checkStack     = new Stack<>();
        clear();
    }

    public final synchronized void clear() {
        this.current = Camp.black;
        for(int i = 0, iend = checkboardSize; i < iend; ++i) {
            for(int j = 0, jend = checkboardSize; j < jend; ++j) {
                checkboard[i][j] = NONE;
            }
        }
        history.clear();
        //do not clear checkStack !
    }

    private final Result win(Camp camp) {
        int [][] winBy = new int [5][];

        for(int i = 0; !checkStack.empty(); ++i) {
            winBy[i] = checkStack.pop();
        }

        clear();
        return new Result(camp == Camp.black? GameResult.blackWin: GameResult.whiteWin, winBy);
    }

    public static final byte campValue(Camp camp) {
        switch (camp) {
            case none:  return NONE;
            case black: return BLACK;
            case white: return WHITE;
        }
        return 0;
    }

    public static final Camp campValue(int camp) {
        switch (camp) {
            case NONE:  return Camp.none;
            case BLACK: return Camp.black;
            case WHITE: return Camp.white;
        }
        return Camp.none;
    }

    public static final String getCampName(Camp camp) {
        switch (camp) {
            case black: return "black";
            case white: return "white";
            default:
            case none:  return "none";
        }
    }

    public static final Z5CGame.Camp getCamp(String campName) {
        switch (campName) {
            case "black": return(Z5CGame.Camp.black);
            case "white": return(Z5CGame.Camp.white);
            default:
            case "none":  return(Z5CGame.Camp.none);
        }
    }

    public final int getStep() {
        return history.size();
    }

    private final Result check(int row, int col, Camp camp) {

        byte cmp = campValue(camp);
        int i, j, count;

        //检查左上右下
        checkStack.clear();
        for(i = row, j = col, count = 0; i >= 0 && j >= 0 && checkboard[i][j] == cmp; --i, --j) {
            checkStack.push(new int[] {i, j});
           if(++count == 5) return win(camp);
        }
        for(i = row + 1, j = col + 1; i < checkboardSize && j < checkboardSize && checkboard[i][j] == cmp; ++i, ++j) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }

        //检查右上左下
        checkStack.clear();
        for(i = row, j = col, count = 0; i >= 0 && j < checkboardSize && checkboard[i][j] == cmp; --i, ++j) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }
        for(i = row + 1, j = col - 1; i < checkboardSize && j >= 0 && checkboard[i][j] == cmp; ++i, --j) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }

        //检查左右
        checkStack.clear();
        for(i = row, j = col, count = 0; j >= 0 && checkboard[i][j] == cmp; --j) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }
        for(j = col + 1; j < checkboardSize && checkboard[i][j] == cmp; ++j) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }

        //检查上下
        checkStack.clear();
        for(i = row, j = col, count = 0; i >= 0 && checkboard[i][j] == cmp; --i) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }
        for(i = row + 1; i < checkboardSize && checkboard[i][j] == cmp; ++i) {
            checkStack.push(new int[] {i, j});
            if(++count == 5) return win(camp);
        }

        if(getStep() == checkboardSize * checkboardSize) return DRAW;

        current = (camp == Camp.black? Camp.white: Camp.black);
        return ACCEPT;
    }

    public final synchronized Stack<int[]> getHistory() {
        return history;
    }

    public final synchronized Result go(int row, int col, Camp camp) {
        if(camp != current  ||  checkboard[row][col] != NONE) return REJECT;

        checkboard[row][col] = campValue(camp);
        history.push(new int[] {row, col, campValue(camp)});
        return check(row, col, camp);
    }

    static final int[] rollbackFail = {-1, -1, -1, -1};

    /**
     * 撤回一步
     * @return 含有两个元素的int二维数组，
     *  第一个元素为撤回的棋子(i, j, camp)，
     *  第二个元素为撤回之后的再前一步(i, j, camp)。
     *  如果没有前一步，则返回的二维数组中相应的位置为null。
     */
    public synchronized int[] rollback(Camp camp) {
        if(camp == current || getStep() == 0) return rollbackFail;

        int [] pre = history.pop();
        checkboard[pre[0]][pre[1]] = NONE;
        current = campValue(pre[2]);
        int[] previous = new int[] {pre[0], pre[1], -1, -1};

        if(getStep() > 0) {
            pre = history.peek();
            previous[2] = pre[0];
            previous[3] = pre[1];
        }
        return previous;
    }

    /**
     * 游戏中止
     */
    public void terminate() {
        clear();
    }
}
